<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<style type="text/css"></style>
</head>
<body>


<script type="text/javascript">
	var vs={};
	vs.radian_to_degree=function(radian){
		var degree=radian*180/Math.PI;
		return degree;
	}
	vs.degree_to_radian=function(degree){
		var radian=Math.PI*degree/180;
		return radian;
	}
	vs.get_distance=function(a,b){
		b=b||{x:0,y:0};
		return Math.sqrt(
				Math.pow((a.x-b.x),2)
				+Math.pow((a.y-b.y),2)
			)
	}
	vs.sin=function(radian){
		var r=Math.sin(radian);
		return r;
	}
	vs.cos=function(radian){
		var r=Math.cos(radian);
		return r;
	}
	vs.tan=function(radian){
		var r=Math.tan(radian);
		return r;
	}
	vs.point_in_polygon=function(point, vs) {
	    // ray-casting algorithm based on
	    // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
	    
	    var x = point[0], y = point[1];
	    
	    var inside = false;
	    for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
	        var xi = vs[i][0], yi = vs[i][1];
	        var xj = vs[j][0], yj = vs[j][1];
	        
	        var intersect = ((yi > y) != (yj > y))
	            && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
	        if (intersect) inside = !inside;
	    }
	    
	    return inside;
	}
	vs.dataURItoBlob=function(dataURI) {
	  // convert base64/URLEncoded data component to raw binary data held in a string
	  var byteString;
	  if (dataURI.split(',')[0].indexOf('base64') >= 0)
	  	byteString = atob(dataURI.split(',')[1]);
	  else
	  	byteString = unescape(dataURI.split(',')[1]);

	  // separate out the mime component
	  var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

	  // write the bytes of the string to a typed array
	  var ia = new Uint8Array(byteString.length);
	  for (var i = 0; i < byteString.length; i++) {
	  	ia[i] = byteString.charCodeAt(i);
	  }

	  return new Blob([ia], {type:mimeString});
	}
	vs.getQueryStringByName=function(name) {
	    var result = location.search.match(new RegExp("[\?\&]" + name + "=([^\&]+)", "i"));
	    if (result == null || result.length < 1) {
	        return "";
	    }
	    return result[1];
	}
</script>


<script type="text/javascript">
			
	const EPSILON=0.000001;
	const TEST_CULL=true;
	var t,u,v;

	function intersect_triangle(orig,dest,vert0,vert1,vert2){
		var dir,edge1,edge2,tvec,pvec,qvec,det,inv_det;
		// debugger;
		dir=Vector.norm(Vector.minus(dest,orig));
		edge1=Vector.minus(vert1,vert0);
		edge2=Vector.minus(vert2,vert0);
		pvec=Vector.cross(dir,edge2);
		det=Vector.dot(edge1,pvec);

		if(TEST_CULL){
			if(det<EPSILON){
				return 0;
			}
			tvec=Vector.minus(orig,vert0);
			u=Vector.dot(tvec,pvec);
			if(u<0.0||u>det){
				return 0;
			}
			qvec=Vector.cross(tvec,edge1);
			v=Vector.dot(dir,qvec);
			if(v<0.0||u+v>det){
				return 0;
			}
			t=Vector.dot(edge2,qvec);
			inv_det=1.0/det;
			t*=inv_det;
			u*=inv_det;
			v*=inv_det;
		}

		return 1;
	}







	var Triangle = (function () {
		function Triangle(norm, vert0,vert1,vert2,surface) {
			this.vert0=vert0;
			this.vert1=vert1;
			this.vert2=vert2;
			this.surface = surface;
			this.normal = function (pos) { return norm; };
			this.intersect = function (ray) {
				var orig,dir,edge1,edge2,tvec,pvec,qvec,det,inv_det;
				orig=ray.start;
				dir=ray.dir;
				vert0=this.vert0;
				vert1=this.vert1;
				vert2=this.vert2;
				// debugger;
				edge1=Vector.minus(vert1,vert0);
				edge2=Vector.minus(vert2,vert0);
				pvec=Vector.cross(dir,edge2);
				det=Vector.dot(edge1,pvec);

				if(det<EPSILON){
					return null;
				}
				tvec=Vector.minus(orig,vert0);
				u=Vector.dot(tvec,pvec);
				if(u<0.0||u>det){
					return null;
				}
				qvec=Vector.cross(tvec,edge1);
				v=Vector.dot(dir,qvec);
				if(v<0.0||u+v>det){
					return null;
				}
				t=Vector.dot(edge2,qvec);
				inv_det=1.0/det;
				t*=inv_det;
				u*=inv_det;
				v*=inv_det;

				debugger

				return { thing: this, ray: ray, dist: t };
			}
		}
		return Triangle;
	}());











	var Sphere = (function () {
		function Sphere(center, radius, surface) {
			this.center = center;
			this.surface = surface;
			this.radius2 = radius * radius;
		}
		Sphere.prototype.normal = function (pos) {
			return Vector.norm(Vector.minus(pos, this.center));
		};
		Sphere.prototype.intersect = function (ray) {
			var eo = Vector.minus(this.center, ray.start);
			var v = Vector.dot(eo, ray.dir);
			var dist = 0;
			if (v >= 0) {
				var disc = this.radius2 - (Vector.dot(eo, eo) - v * v);
				if (disc >= 0) {
					dist = v - Math.sqrt(disc);
				}
			}
			if (dist === 0) {
				return null;
			}
			else {
				// debugger;
				return { thing: this, ray: ray, dist: dist };
			}
		};
		return Sphere;
	}());













	var Vector = (function () {
		function Vector(x, y, z) {
			this.x = x;
			this.y = y;
			this.z = z;
		}
		Vector.times = function (k, v) {
			return new Vector(k * v.x, k * v.y, k * v.z);
		};
		Vector.minus = function (v1, v2) {
			return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
		};
		Vector.plus = function (v1, v2) {
			return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
		};
		Vector.dot = function (v1, v2) {
			return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
		};
		Vector.mag = function (v) {
			return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
		};
		Vector.norm = function (v) {
			var mag = Vector.mag(v);
			var div = (mag === 0) ? Infinity : 1.0 / mag;
			return Vector.times(div, v);
		};
		Vector.cross = function (v1, v2) {
			return new Vector(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x);
		};
		return Vector;
	}());
	



	var identityMatrix = [
		1, 0, 0, 0,
		0, 1, 0, 0,
		0, 0, 1, 0,
		0, 0, 0, 1
	];

	var sin = Math.sin;
	var cos = Math.cos;
	
	function multiplyMatrixAndPoint(matrix, vector) {

		var point=[
			vector.x,
			vector.y,
			vector.z,
			1
		]
		
		//Give a simple variable name to each part of the matrix, a column and row number
		var c0r0 = matrix[ 0], c1r0 = matrix[ 1], c2r0 = matrix[ 2], c3r0 = matrix[ 3];
		var c0r1 = matrix[ 4], c1r1 = matrix[ 5], c2r1 = matrix[ 6], c3r1 = matrix[ 7];
		var c0r2 = matrix[ 8], c1r2 = matrix[ 9], c2r2 = matrix[10], c3r2 = matrix[11];
		var c0r3 = matrix[12], c1r3 = matrix[13], c2r3 = matrix[14], c3r3 = matrix[15];
		
		//Now set some simple names for the point
		var x = point[0];
		var y = point[1];
		var z = point[2];
		var w = point[3];
		
		//Multiply the point against each part of the 1st column, then add together
		var resultX = (x * c0r0) + (y * c0r1) + (z * c0r2) + (w * c0r3);
		
		//Multiply the point against each part of the 2nd column, then add together
		var resultY = (x * c1r0) + (y * c1r1) + (z * c1r2) + (w * c1r3);
		
		//Multiply the point against each part of the 3rd column, then add together
		var resultZ = (x * c2r0) + (y * c2r1) + (z * c2r2) + (w * c2r3);
		
		//Multiply the point against each part of the 4th column, then add together
		var resultW = (x * c3r0) + (y * c3r1) + (z * c3r2) + (w * c3r3);
		
		// return [resultX, resultY, resultZ, resultW];
		return new Vector(resultX, resultY, resultZ);
	}


	function rotateAroundXAxis(a) {
	  
	  return [
	       1,       0,        0,     0,
	       0,  cos(a),  -sin(a),     0,
	       0,  sin(a),   cos(a),     0,
	       0,       0,        0,     1
	  ];
	}

	function rotateAroundYAxis(a) {
	  
	  return [
	     cos(a),   0, sin(a),   0,
	          0,   1,      0,   0,
	    -sin(a),   0, cos(a),   0,
	          0,   0,      0,   1
	  ];
	}

	function rotateAroundZAxis(a) {
	  
	  return [
	    cos(a), -sin(a),    0,    0,
	    sin(a),  cos(a),    0,    0,
	         0,       0,    1,    0,
	         0,       0,    0,    1
	  ];
	}






	var Color = (function () {
		function Color(r, g, b) {
			this.r = r;
			this.g = g;
			this.b = b;
		}
		Color.scale = function (k, v) {
			return new Color(k * v.r, k * v.g, k * v.b);
		};
		Color.plus = function (v1, v2) {
			return new Color(v1.r + v2.r, v1.g + v2.g, v1.b + v2.b);
		};
		Color.times = function (v1, v2) {
			return new Color(v1.r * v2.r, v1.g * v2.g, v1.b * v2.b);
		};
		Color.toDrawingColor = function (c) {
			var legalize = function (d) { return d > 1 ? 1 : d; };
			return {
				r: Math.floor(legalize(c.r) * 255),
				g: Math.floor(legalize(c.g) * 255),
				b: Math.floor(legalize(c.b) * 255)
			};
		};
		return Color;
	}());
	Color.white = new Color(1.0, 1.0, 1.0);
	Color.grey = new Color(0.5, 0.5, 0.5);
	Color.black = new Color(0.0, 0.0, 0.0);
	Color.background = Color.black;
	Color.defaultColor = Color.black;

















	var Surfaces;
	(function (Surfaces) {
		Surfaces.shiny = {
			diffuse: function (pos) { return Color.white; },
			specular: function (pos) { return Color.grey; },
			reflect: function (pos) { return 0.7; },
			roughness: 250
		};
		Surfaces.checkerboard = {
			diffuse: function (pos) {
				// debugger;
				if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) {
					return Color.white;
				}
				else {
					return Color.black;
				}
			},
			specular: function (pos) { return Color.white; },
			reflect: function (pos) {
				if ((Math.floor(pos.z) + Math.floor(pos.x)) % 2 !== 0) {
					return 0.1;
				}
				else {
					return 0.7;
				}
			},
			roughness: 150
		};
	})(Surfaces || (Surfaces = {}));












	var Camera = (function () {
		function Camera(pos, lookAt) {
			this.pos = pos;
			var down = new Vector(0.0, -1.0, 0.0);
			this.forward = Vector.norm(Vector.minus(lookAt, this.pos));
			this.right = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, down)));
			this.up = Vector.times(1.5, Vector.norm(Vector.cross(this.forward, this.right)));
		}
		return Camera;
	}());









	var RayTracer = (function () {
		function RayTracer() {
			this.maxDepth = 5;
		}
		RayTracer.prototype.intersections = function (ray, scene) {
			var closest = +Infinity;
			var closestInter = undefined;
			for (var i in scene.things) {
				var inter = scene.things[i].intersect(ray);
				if (inter != null && inter.dist < closest) {
					closestInter = inter;
					closest = inter.dist;
				}
			}
			return closestInter;
		};
		RayTracer.prototype.testRay = function (ray, scene) {
			var isect = this.intersections(ray, scene);
			if (isect != null) {
				return isect.dist;
			}
			else {
				return undefined;
			}
		};
		RayTracer.prototype.traceRay = function (ray, scene, depth) {
			var isect = this.intersections(ray, scene);
			if (isect === undefined) {
				return Color.background;
			}
			else {
				// return this.shade(isect, scene, depth);
				return Color.white;
			}
		};
		RayTracer.prototype.shade = function (isect, scene, depth) {
			var d = isect.ray.dir;
			var pos = Vector.plus(Vector.times(isect.dist, d), isect.ray.start);
			var normal = isect.thing.normal(pos);
			var reflectDir = Vector.minus(d, Vector.times(2, Vector.times(Vector.dot(normal, d), normal)));
			var naturalColor = Color.plus(Color.background, this.getNaturalColor(isect.thing, pos, normal, reflectDir, scene));
			var reflectedColor = (depth >= this.maxDepth) ? Color.grey : this.getReflectionColor(isect.thing, pos, normal, reflectDir, scene, depth);
			return Color.plus(naturalColor, reflectedColor);
		};
		RayTracer.prototype.getReflectionColor = function (thing, pos, normal, rd, scene, depth) {
			return Color.scale(thing.surface.reflect(pos), this.traceRay({ start: pos, dir: rd }, scene, depth + 1));
		};
		RayTracer.prototype.getNaturalColor = function (thing, pos, norm, rd, scene) {
			var _this = this;
			var addLight = function (col, light) {
				var ldis = Vector.minus(light.pos, pos);
				var livec = Vector.norm(ldis);
				// debugger;
				var neatIsect = _this.testRay({ start: pos, dir: livec }, scene);
				var isInShadow = (neatIsect === undefined) ? false : (neatIsect <= Vector.mag(ldis));
				if (isInShadow) {
					return col;
				}
				else {
					var illum = Vector.dot(livec, norm);
					var lcolor = (illum > 0) ? Color.scale(illum, light.color)
						: Color.defaultColor;
					var specular = Vector.dot(livec, Vector.norm(rd));
					var scolor = (specular > 0) ? Color.scale(Math.pow(specular, thing.surface.roughness), light.color)
						: Color.defaultColor;
					return Color.plus(col, Color.plus(Color.times(thing.surface.diffuse(pos), lcolor), Color.times(thing.surface.specular(pos), scolor)));
				}
			};
			return scene.lights.reduce(addLight, Color.defaultColor);
		};
		RayTracer.prototype.render = function (scene, ctx, screenWidth, screenHeight) {
			var getPoint = function (x, y, camera) {
				var recenterX = function (x) { 
					aaa=(x - (screenWidth / 2.0)) / 2.0 / screenWidth;
					return aaa; 
				};
				var recenterY = function (y) {
					aaa=-(y - (screenHeight / 2.0)) / 2.0 / screenHeight;
					return aaa; 
				};


				return Vector.norm(//black
						Vector.plus(//yellow
							camera.forward, //blue
							Vector.plus(//orange
								Vector.times(recenterX(x), camera.right /*color 81 81 81*/ ),//white
								Vector.times(recenterY(y), camera.up/*green*/)//red
							)
						)
					);
			};

			for (var y = 0; y < screenHeight; y++) {
				for (var x = 0; x < screenWidth; x++) {

					var aaa=getPoint(x, y, scene.camera);

					var color = this.traceRay({ start: scene.camera.pos, dir: aaa }, scene, 0);
					var c = Color.toDrawingColor(color);
					ctx.fillStyle = "rgb(" + String(c.r) + ", " + String(c.g) + ", " + String(c.b) + ")";
					ctx.fillRect(x, y, x + 1, y + 1);
				}
			}
		};
		return RayTracer;
	}());









	var canvas_size=100;
	var rayTracer = new RayTracer();

	function draw(scene){
		rayTracer.render(scene, ctx, canvas_size, canvas_size);
	}


	function exec() {
		var canv = document.createElement("canvas");
		canv.width = canvas_size;
		canv.height = canvas_size;
		document.body.appendChild(canv);
		window.ctx = canv.getContext("2d");
		// draw(new Vector(0,0,0),         new Vector( 2  , 0  ,  4 ),               new Vector( 9  ,  0 ,  1 ));



		// draw(
		// 	multiplyMatrixAndPoint(rotateAroundXAxis(.3),new Vector(0,0,0)),         
		// 	multiplyMatrixAndPoint(rotateAroundXAxis(.3),new Vector(2,0,4)),               
		// 	multiplyMatrixAndPoint(rotateAroundXAxis(.3),new Vector(9,0,1))
		// );
	}
	exec();



	var i=0;

	// function step(){
	// 	i+=.1;
	// 	draw(
	// 		multiplyMatrixAndPoint(rotateAroundYAxis(i),new Vector(0,0,0)),         
	// 		multiplyMatrixAndPoint(rotateAroundYAxis(i),new Vector(2,0,4)),               
	// 		multiplyMatrixAndPoint(rotateAroundYAxis(i),new Vector(9,0,1))
	// 	);
	// 	requestAnimationFrame(step);
	// }
	// requestAnimationFrame(step);

	// setInterval(function(){
	// 	i+=.1;
	// 	draw(
	// 		multiplyMatrixAndPoint(rotateAroundYAxis(i),new Vector(0,0,0)),         
	// 		multiplyMatrixAndPoint(rotateAroundYAxis(i),new Vector(2,0,4)),               
	// 		multiplyMatrixAndPoint(rotateAroundYAxis(i),new Vector(9,0,1))
	// 	);
	// },100)

	draw({
		things: [

			new Triangle(
				new Vector(0.0, 0, 1), 

				new Vector(0,0,0),         
				new Vector(2,0,4),               
				new Vector(9,0,0),        
				Surfaces.shiny
			),

			// new Sphere(new Vector(0,0,0), 3, Surfaces.shiny),

		],
		lights: [{ pos: new Vector(-2.0, 2.5, 0.0), color: new Color(0.49, 0.07, 0.07) },
			{ pos: new Vector(1.5, 2.5, 1.5), color: new Color(0.07, 0.07, 0.49) },
			{ pos: new Vector(1.5, 2.5, -1.5), color: new Color(0.07, 0.49, 0.071) },
			{ pos: new Vector(0.0, 3.5, 0.0), color: new Color(0.21, 0.21, 0.35) }],
		camera: new Camera(new Vector(0, 10, -20), new Vector(0,0,0))
	});


	document.onmousemove=function(e){
		mouse_y=e.pageX/100;
		mouse_x=e.pageY/100;
		// console.log(vs.radian_to_degree(mouse_y),vs.radian_to_degree(mouse_x));
		draw({
			things: [

				new Triangle(
					new Vector(0.0, 0, 1), 
					
					multiplyMatrixAndPoint(
						rotateAroundYAxis(mouse_y),
						multiplyMatrixAndPoint(
							rotateAroundXAxis(mouse_x),
							new Vector(0,0,0)
						)
					),         
					multiplyMatrixAndPoint(
						rotateAroundYAxis(mouse_y),
						multiplyMatrixAndPoint(
							rotateAroundXAxis(mouse_x),
							new Vector(2,0,4)
						)
					),               
					multiplyMatrixAndPoint(
						rotateAroundYAxis(mouse_y),
						multiplyMatrixAndPoint(
							rotateAroundXAxis(mouse_x),
							new Vector(9,0,0)
						)
					),     
					Surfaces.shiny
				),

				new Triangle(
					new Vector(0.0, 0, 1), 
					
					multiplyMatrixAndPoint(
						rotateAroundYAxis(mouse_y),
						multiplyMatrixAndPoint(
							rotateAroundXAxis(mouse_x),
							new Vector(0,0,0)
						)
					),               
					multiplyMatrixAndPoint(
						rotateAroundYAxis(mouse_y),
						multiplyMatrixAndPoint(
							rotateAroundXAxis(mouse_x),
							new Vector(9,0,0)
						)
					),          
					multiplyMatrixAndPoint(
						rotateAroundYAxis(mouse_y),
						multiplyMatrixAndPoint(
							rotateAroundXAxis(mouse_x),
							new Vector(2,0,4)
						)
					),    
					Surfaces.shiny
				),

				// new Sphere(new Vector(0,0,0), 3, Surfaces.shiny),

			],
			lights: [{ pos: new Vector(-2.0, 2.5, 0.0), color: new Color(0.49, 0.07, 0.07) },
				{ pos: new Vector(1.5, 2.5, 1.5), color: new Color(0.07, 0.07, 0.49) },
				{ pos: new Vector(1.5, 2.5, -1.5), color: new Color(0.07, 0.49, 0.071) },
				{ pos: new Vector(0.0, 3.5, 0.0), color: new Color(0.21, 0.21, 0.35) }],
			camera: new Camera(new Vector(0, 10, -20), new Vector(0,0,0))
		});
	}


	document.onmousemove2=function(e){
		mouse_y=e.pageX/100;
		mouse_x=e.pageY/100;
		// console.log(vs.radian_to_degree(mouse_y),vs.radian_to_degree(mouse_x));
		draw({
			things: [

				new Triangle(
					new Vector(0.0, 0, 1), 
					
					multiplyMatrixAndPoint(
						rotateAroundXAxis(mouse_x),
						multiplyMatrixAndPoint(
							rotateAroundYAxis(mouse_y),
							new Vector(0,0,0)
						)
					),         
					multiplyMatrixAndPoint(
						rotateAroundXAxis(mouse_x),
						multiplyMatrixAndPoint(
							rotateAroundYAxis(mouse_y),
							new Vector(2,0,4)
						)
					),               
					multiplyMatrixAndPoint(
						rotateAroundXAxis(mouse_x),
						multiplyMatrixAndPoint(
							rotateAroundYAxis(mouse_y),
							new Vector(9,0,0)
						)
					), 
					Surfaces.shiny
				),

				// new Sphere(new Vector(0,0,0), 3, Surfaces.shiny),

			],
			lights: [{ pos: new Vector(-2.0, 2.5, 0.0), color: new Color(0.49, 0.07, 0.07) },
				{ pos: new Vector(1.5, 2.5, 1.5), color: new Color(0.07, 0.07, 0.49) },
				{ pos: new Vector(1.5, 2.5, -1.5), color: new Color(0.07, 0.49, 0.071) },
				{ pos: new Vector(0.0, 3.5, 0.0), color: new Color(0.21, 0.21, 0.35) }],
			camera: new Camera(new Vector(0, 10, -20), new Vector(0,0,0))
		});
	}

	document.onkeydown=function(e){
		if(e.code=='ArrowUp'){

		}
		else if(e.code=='ArrowDown'){

		}
		else if(e.code=='ArrowLeft'){

		}
		else if(e.code=='ArrowRight'){

		}
	}


</script>
</body>
</html>
